#include <string.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>

//要以文件IO的思想去学习这个IPC通信
/*
实现两个有血缘关系进程的通信    
注意事项：
    1、父进程创建的信号处理函数sigchld_handler（）回收的子进程是比父进程先退出的进程，如果有进程比父进程退出慢，
       则父进程退出后，未被回收的进程就交给init进程管理了，到时候由1号init进程回收。
    2、pause() 唤醒的信号处理函数中添加了一个打印函数导致子进程被阻塞了，处理流程被影响了
    原因是：sighandler 函数原本应该是一个可重入函数，因为它需要在信号处理的上下文中被安全地调用。
    但是，你在 sighandler 函数中使用了 printf 函数，printf 是一个不可重入函数，因为它依赖于全局的
    标准输出缓冲区。当 sighandler 函数被调用时，printf 可能会修改标准输出缓冲区的状态，从而导致后
    续的 pause 函数无法正常工作，甚至可能会导致程序崩溃。
    3、fork()之前，创建一块共享内存，释放的时候只需要父进程或者子进程一个进程去调用shmctl()函数操作即可，
    如果多个进程都操作删除，会报参数错误。
*/

void sighandler(int signum)
{
    //这个打印不可加，因为父子进程都可能会调用该函数
    //printf("process pid = [%d], recv [%s]\n", getpid(), signum);
    return ;
}

void sigchld_handler(int signum){
    printf("signum = [%d]\n", signum);
    /*
        -1: 回收任意一个子进程
        NULL：子进程退出原因不在乎
        WNOHANG：如果没有子进程退出，立即返回
        返回值：>0: 回收成功的子进程id, ==0:子进程还在运行中, <0 出现错误
    */
    pid_t pid;
    while((pid = waitpid(-1, NULL, WNOHANG)) > 0){ 
        printf("回收成功：pid = [%d]\n", pid);
    }
    return;
}

int main(int argc, char *argv[]){
    pid_t pid;
    char *ptr = NULL;
    char buf[1024] = {0};
    int ret;
    int len = 0;

    //在内核中创建一块共享内存
    int shmid = shmget(IPC_PRIVATE, 1024, IPC_CREAT|0777);
    if(shmid < 0){
        perror("shmget() error:");
        exit(-1);
    }

    //查看共享内存信息
    system("ipcs -m");
    
    pid = fork();
    if(pid < 0){
        perror("fork() error:");
        exit(-1);
    }

    if(pid > 0){
        printf("child pid = [%d], parent pid = [%d]\n", pid, getpid());
        signal(SIGUSR1, sighandler); //注册信号处理函数，用于父子间同步交互
        signal(SIGCHLD, sigchld_handler);
        ptr = (char *)shmat(shmid, NULL, 0);
        if(ptr == NULL){
            perror("shmat() error:");
            exit(-1);
        }
        while(1){
            if(!strncmp(ptr, "exit", 4)){
                break;
            }
            memset(ptr, 0, 1024);
            printf("father write:");
            fgets(ptr, 1024, stdin);
            len = strlen(ptr);
            if (len > 0 && ptr[len - 1] == '\n') {
                ptr[len - 1] = '\0';  // 替换换行符
            }
            kill(pid, SIGUSR2);
            pause(); //wait 子进程读取共享内存中的数据
        }
        //将进程里的地址映射删除
        ret = shmdt(ptr);
        if(ret < 0){
            perror("shmdt() error:");
            exit(-1);
        }
    }else{
        signal(SIGUSR2, sighandler); //注册信号处理函数，用于父子间同步交互
        ptr = (char *)shmat(shmid, NULL, 0);
        if(ptr == NULL){
            perror("shmat() error:");
            exit(-1);
        }
        while(1){
            pause(); //wait 父进程写数据到共享内存
            printf("child read:%s\n", ptr);
            kill(getppid(), SIGUSR1);
            if(!strncmp(ptr, "exit", 4)){
                break;
            }
        }
        //将进程里的地址映射删除
        ret = shmdt(ptr);
        if(ret < 0){
            perror("shmdt() error:");
            exit(-1);
        }
        return 0;
    }

    //删除共享内存, 只需要父进程删除就可以了,如果子进程也操作删除，会报参数错误，因为这个是一块共享区域
    ret = shmctl(shmid, IPC_RMID, NULL);
    if(ret < 0){
        perror("shmctl() error:");
        exit(-1);
    }
    //查看共享内存信息
    system("ipcs -m");
    return 0;
}
